class Emitter {
  private _events: any;

  constructor() {
    this._events = Object.create(null);
  }

  on(eventName, callback, automatic?, nameSpace?) {
    const { callbacks = [], cache = null } = this._events[eventName] || {};
    if (nameSpace) {
      // eslint-disable-next-line no-param-reassign
      callback.space = nameSpace;
    }
    callbacks.push(callback);
    this._events[eventName] = {
      callbacks,
      cache,
    };
    if (automatic && cache) {
      callback(...cache);
    }
  }

  once(eventName, callback) {
    const one = (...args) => {
      callback(...args);
      this.off(eventName, one);
    };
    one.callback = callback;
    this.on(eventName, one);
  }

  off(eventName, callback, nameSpace?) {
    if (!this._events[eventName]) return;
    const callbacks = this._events[eventName].callbacks || [];
    // eslint-disable-next-line array-callback-return
    this._events[eventName].callbacks = callbacks.filter((fn) => {
      if (nameSpace) {
        return fn.space !== nameSpace;
      }
      const flag =
        fn.toString() !== callback.toString() &&
        String(fn.callback) !== callback.toString();
      return flag;
    });
  }

  emit(eventName, ...args) {
    const { callbacks = [] } = this._events[eventName] || {};
    this._events[eventName] = {
      callbacks,
      cache: args,
    };
    callbacks.forEach((callback) => {
      callback(...args);
    });
  }
}

const emitter = new Emitter();
export default emitter;
